//standard
#include <istream>
#include <numeric>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <deque>
#include <cstdlib>

// boost
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/range.hpp>

// components
#include <index_string.h>
#include <perlutil.h>
#include <ultoa.h>
#include <progress_indicator.h>
#include <print_error.h>
#include <portable_timer.h>
#include <open_fstream.h>
#define DEBUG_LEVEL 6
#include <debug_func.h>
#include <open_stream_name_with_substitutions.h>

#include "get_arg.h"

using boost::regex;
using boost::match_results;
using boost::regex_search;
using perlutil::perl_join;
using std::vector;
using std::deque;
using std::istringstream;
using std::string;
using std::ofstream;
using std::ifstream;
using std::cout;
using std::cin;
using std::cerr;
using boost::algorithm::replace_range;
using boost::algorithm::find_first;
typedef boost::iterator_range<string::iterator> t_sub_str;

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	main

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
struct t_tree_str
{
	string tree;
	string placeholder;
	t_tree_str(const string& placeholder_, const string& tree_):
			tree(tree_), placeholder(placeholder_){}
};


bool get_non_blank_line(std::istream& is, string& str, unsigned& cnt_lines)
{
	// extra test at beginning
	if (!cin)
		return false;

	while (1)
	{
		// failed to get line
		if(!getline(cin, str))
			return false;

		++cnt_lines;

		if (!str.empty())
			return true;
	}

	// should never be here
	return false;
}

void load_file_into_str(const string& file_name, std::string& contents)
{
	ifstream ifs;
	std::istream::pos_type file_size = open_fstream(ifs, file_name);
	std::istreambuf_iterator<char> begin(ifs), end;

	contents.reserve(file_size + std::streamoff(1));
	contents.assign(begin,  end);
}

void check_remaining_tree_stubs(const string& tree_str, const string& file_name, ostream& CERR)
{
	//
	//	find tree stubs using regex
	//
	string::const_iterator start	= tree_str.begin();
	string::const_iterator end		= tree_str.end();
	match_results<string::const_iterator> found_tree_stub;
	regex tree_stubs("(SUB_X*\\d+)");
	vector<string> remaining_tree_stubs;
	while(regex_search(start, end, found_tree_stub, tree_stubs, boost::match_default))
	{
		remaining_tree_stubs.push_back(string(	found_tree_stub[1].first,
												found_tree_stub[1].second));
		// update search position:
		start = found_tree_stub[0].second;
	}

	//
	//	If any tree stubs print warning
	//
	if (remaining_tree_stubs.size())
	{
		std_print_warning(CERR,	"The following tree stubs without matching "
										"sub-trees were found in the file\n"
										"\"" + file_name + "\":\n"
										"\t" + perl_join(remaining_tree_stubs, "\n\t"));
	}

}

int main (int argc, char *argv[])
{
	debug_func_cerr(5);

	// we use cout to print progress so flush per call
	//cout.setf(std::ios_base::unitbuf);

	std::ios::sync_with_stdio(false);

	t_program_args args;
	try
	{
		if (!process_command_line_options(argc, argv, args))
			return 1;


	//    char buff[4096];
	//    cin.rdbuf()->pubsetbuf(buff, sizeof buff ); // enable buffering


		deque<deque<t_tree_str> > trees;
		t_index_string			file_names;

		//
		//	Read all subbranches from STDIN
		//
		unsigned cnt_lines = 0;
		args.VERBOSE() << "\tRejoin sub-branches\n";
		{
			args.VERBOSE() << "\tRead in all sub-branches from STDIN...\n";
			t_progress_indicator dots(args.VERBOSE(1),  1);
			while (cin)
			{

				//
				// get accession
				//
				string name_line;
				if (!get_non_blank_line(cin, name_line, cnt_lines))
					break;
				if (name_line[0] != '>')
					throw std::runtime_error("Tree name missing from line #" + ultoa(cnt_lines));
				name_line.erase(0, 1);

				//
				// break into name and file
				//
				istringstream ostrm(name_line);
				string placeholder, file_name;
				ostrm >> placeholder >> file_name;
				if (placeholder.empty())
					throw std::runtime_error("Tree has no name on line #" + ultoa(cnt_lines));
				if (file_name.empty())
					throw std::runtime_error("Tree has no file on line #" + ultoa(cnt_lines));

				//
				//	get tree
				//
				string tree_line;
				if (!get_non_blank_line(cin, tree_line, cnt_lines))
					throw std::runtime_error("Tree not found on line #" + ultoa(cnt_lines));

				// find and remove everything after semi-colon
				string::size_type pos = tree_line.find(';');
				if (pos == string::npos)
					throw std::runtime_error("Tree terminated with semicolon on line #" + ultoa(cnt_lines));
				tree_line.erase(pos);

				// index filename and add to trees
				bool added = false;
				unsigned index = file_names.index(file_name, added);
				if (added)
					trees.push_back(deque<t_tree_str>());
				trees[index].push_back(t_tree_str(placeholder,  tree_line));
				++dots;
			}
		}

		args.VERBOSE() << "\tLook all trees and substitute sub-branch for stubs...\n";
		{
			t_progress_indicator dots(args.VERBOSE(1),  1);

			//
			//	for each file open tree and substitute
			//
			string original_trees_str;	// keep here to avoid having to reallocated memory
			for (unsigned i = 0; i < file_names.size(); ++i)
			{
				args.VERBOSE(2) << "\tSubstitute branches in file \""
						<< file_names.string_at(i) << "\"...\n";
				//
				// open file
				//
				load_file_into_str(file_names.string_at(i), original_trees_str);

				//
				// replace all
				//
				vector<string> placeholders_not_found;
				for (unsigned j = 0; j < trees[i].size(); ++j)
				{
					t_sub_str found = find_first(original_trees_str, trees[i][j].placeholder);
					if (found)
						replace_range(original_trees_str, found, trees[i][j].tree);
					else
						placeholders_not_found.push_back(trees[i][j].placeholder);
				}
				//
				//	If any tree stubs not found, print warning
				//
				if (placeholders_not_found.size())
				{
					std_print_warning(args.CERR(),
									  "The following tree stubs were missing from the file\n"
											"\"" + file_names.string_at(i) + "\":\n"
											"\t" + perl_join(placeholders_not_found, "\n\t"));
				}



				//
				// check for remaining tree stubs
				//
				{
					check_remaining_tree_stubs(original_trees_str, file_names.string_at(i),
												args.CERR());
				}



				// save to new file
				ofstream ofs;
				string output_file_name;
				open_stream_with_substitutions(	ofs,
												output_file_name,
												args.output_path,
												file_names.string_at(i));
				ofs << original_trees_str;
				++dots;
			}
		}
	}
	catch(std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return false;
	}

	args.VERBOSE(1) << "\tAll finished!!\n\n";

	return 0;
}


